//
// SoapTrace.Net - A soap message monitoring utility. 
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using SoapTrace.Core.Communication;
using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using WinPcap;

namespace SoapTrace.Core.Diagnostics
{
	/// <summary>
	/// File that contains packet information received by the Soap Trace
	/// application.
	/// </summary>
	public class PacketLogFile
	{
		/// <summary>
		/// Xml element name of a log entry item.
		/// </summary>
		private static readonly string LogEntryElementName = "Packet";
		
		/// <summary>
		/// Name of the root element of the log file.
		/// </summary>
		private static readonly string RootElementName = "Packets";
		
		/// <summary>
		/// String used to format the date time in the log file.
		/// </summary>
		private static readonly string DateTimeFormatString = "yyyy-MM-dd HH:mm:ss.ff";
		
		/// <summary>
		/// Name of the attribute holding the packet received time.
		/// </summary>
		private static readonly string PacketReceivedTimeAttributeName = "Time";
		
		/// <summary>
		/// Name of the attribute that stores the remote server name.
		/// </summary>
		private static readonly string RemoteServerAttributeName = "ServerFilter";
		
		/// <summary>
		/// Name of the attribute that stores the remote server port.
		/// </summary>
		private static readonly string RemoteServerPortAttributeName = "PortFilter";
		
		/// <summary>
		/// Name of the attribute that stores the packet type.
		/// </summary>
		private static readonly string PacketTypeAttributeName = "PacketType";
		
		/// <summary>
		/// Name of the element that contains the actual packet bytes.
		/// </summary>
		private static readonly string PacketBytesElementName = "Bytes";
		
		/// <summary>
		/// Creates a new instance of the <see cref="PacketLogFile"/>.
		/// </summary>
		private PacketLogFile( )
		{
		}
		
		/// <summary>
		/// Saves packet log entries to the specified file.
		/// </summary>
		/// <param name="filename">The filename to used.</param>
		/// <param name="entries">The log entries to be saved.</param>
		public static void Save( string filename, PacketLogEntryCollection entries )
		{
			StreamWriter Writer = new StreamWriter( filename, false, Encoding.UTF8 );
			Save( Writer, entries );
		}
		
		/// <summary>
		/// Saves packet log entries to the specified file.
		/// </summary>
		/// <remarks>
		/// Uses a custom routine to save the file as xml since the 
		/// packet object does not support xml serialisation.
		/// </remarks>
		/// <param name="writer">The writer to use to save packet
		/// information.</param>
		/// <param name="entries">The log entries to be saved.</param>
		public static void Save( TextWriter writer, PacketLogEntryCollection entries )
		{
			XmlTextWriter XmlTextWriter = new XmlTextWriter( writer );
			XmlTextWriter.Formatting = Formatting.Indented;
			
			XmlTextWriter.WriteStartElement( RootElementName );
			
			foreach( PacketLogEntry Entry in entries )
			{
				WriteLogEntry( XmlTextWriter, Entry );
			}
			
			XmlTextWriter.WriteEndElement( );
			XmlTextWriter.Close( );
		}
		
		/// <summary>
		/// Loads all the entries in the log file.
		/// </summary>
		/// <param name="filename">The filename containing the log 
		/// entries.</param>
		/// <returns>The log entries in the file.</returns>
		public static PacketLogEntryCollection Load( string filename )
		{
			StreamReader Reader = new StreamReader( filename, true );
			return Load( Reader );
		}
		
		/// <summary>
		/// Loads all the entries in the log file.
		/// </summary>
		/// <param name="reader">The reader that will be used to 
		/// read the log file.</param>
		/// <returns>The log entries in the file.</returns>
		public static PacketLogEntryCollection Load( TextReader reader )
		{
			PacketLogEntryCollection Entries = new PacketLogEntryCollection( );
						
			XmlTextReader XmlTextReader = new XmlTextReader( reader );
			
			try
			{
				while( XmlTextReader.Read( ) )
				{
					switch( XmlTextReader.NodeType ) 
					{
						case XmlNodeType.Element:
						{
							if( String.Compare( LogEntryElementName, XmlTextReader.Name ) == 0 )
							{
								PacketLogEntry Entry = ReadLogEntry( XmlTextReader );
								Entries.Add( Entry );
							}
						}
	               		break;
					}
				}
			}
			finally
			{
				if( XmlTextReader != null )
				{
					XmlTextReader.Close( );
				}
			}
			
			return Entries;
		}
		
		/// <summary>
		/// Writes the log entry to the xml text writer.
		/// </summary>
		/// <param name="writer">The xml text writer to use.</param>
		/// <param name="entry">The log entry to write.</param>
		private static void WriteLogEntry( XmlTextWriter writer, PacketLogEntry entry )
		{
			writer.WriteStartElement( LogEntryElementName );
			
			writer.WriteAttributeString( PacketReceivedTimeAttributeName, 
			                            entry.PacketReceivedTime.ToString( DateTimeFormatString, DateTimeFormatInfo.InvariantInfo ) );
			
			// Write filter information.
			writer.WriteAttributeString( RemoteServerAttributeName, entry.RemoteServerFilter );
			writer.WriteAttributeString( RemoteServerPortAttributeName, entry.RemoteServerPortFilter.ToString( ) );
			
			// Packet type.
			writer.WriteAttributeString( PacketTypeAttributeName, entry.PacketType );
			
			WritePacket( writer, entry.Packet );
			
			writer.WriteEndElement( );
		}
		
		/// <summary>
		/// Writes a data packet using the specified xml text writer.
		/// </summary>
		/// <param name="writer">The xml text writer to use.</param>
		/// <param name="packet">A data packet to write.</param>
		private static void WritePacket( XmlTextWriter writer, Packet packet )
		{
			byte[ ] Bytes = packet.GetBytes( );
			
			StringBuilder PacketBytesString = new StringBuilder( );
			
			foreach( byte CurrentByte in Bytes )
			{
				PacketBytesString.Append( CurrentByte.ToString( "X2", CultureInfo.InvariantCulture ) );
				PacketBytesString.Append( ' ' );
			}
			
			writer.WriteElementString( PacketBytesElementName, PacketBytesString.ToString( ) );
		}
		
		/// <summary>
		/// Reads the log entry from the xml text reader.
		/// </summary>
		/// <remarks>Assumes that the current text reader node is the log entry 
		/// element.</remarks>
		/// <param name="reader">The xml text reader to use.</param>
		/// <returns>A log entry.</returns>
		private static PacketLogEntry ReadLogEntry( XmlTextReader reader )
		{
			// Read packet received time.
			string DateTimeString = reader.GetAttribute( PacketReceivedTimeAttributeName );
			DateTime ReceivedTime = DateTime.ParseExact( DateTimeString, DateTimeFormatString, DateTimeFormatInfo.InvariantInfo );
			
			// Read filter info.
			string RemoteServer = reader.GetAttribute( RemoteServerAttributeName );
			int RemoteServerPort = Convert.ToInt32( reader.GetAttribute( RemoteServerPortAttributeName ) );
			
			string PacketType = reader.GetAttribute( PacketTypeAttributeName );
			
			// Read packet.
			Packet Packet = ReadPacket( reader );
			
			PacketLogEntry Entry = 
				new PacketLogEntry( RemoteServer, RemoteServerPort, ReceivedTime, Packet, PacketType );
			return Entry;
		}		
		
		/// <summary>
		/// Reads in a packet from the xml text reader. 
		/// </summary>
		/// <remarks>
		/// This method assumes that the current text reader node is
		/// the LogEntry element.
		/// </remarks>
		/// <param name="reader">The xml text reader used to 
		/// read in the packet.</param>
		/// <returns>A data packet.</returns>
		private static Packet ReadPacket( XmlTextReader reader )
		{
			Packet Packet = null;
			
			// Find bytes element.
			
			while( reader.Read( ) && ( reader.NodeType != XmlNodeType.EndElement ) )
			{
				switch( reader.NodeType ) 
				{
					case XmlNodeType.Element:
					{
						// Is it a bytes element?
						
						if( String.Compare( reader.Name, PacketBytesElementName ) == 0 )
						{
							Packet = new Packet( GetBytesFromString( reader.ReadElementString( ) ) );
						}
					}
					break;
				}
			}
			
			return Packet;
		}
		
		/// <summary>
		/// <para>Gets a byte array from an ascii string of hex numbers.  
		/// The string has the format:</para>
		/// <para>
		/// <code>
		/// AA BB CC DD 01 02 03 04 BB CC BB CC 01 02 02 01 
		/// </code>
		/// </para>
		/// </summary>
		/// <param name="data">The byte array string.</param>
		/// <returns>A byte array.</returns>
		public static byte[ ] GetBytesFromString( string data )
		{
			ByteArray Bytes = new ByteArray( );

			// Split the items.

			string[ ] DataItems = data.Split( ' ' );

			// Extract all bytes from the split items.
				
			for( int i = 0; i < DataItems.Length; ++i )
			{
				string Item = DataItems[ i ].Trim( );
				
				if( Item.Length > 0 )
				{
					byte ByteToAdd = Convert.ToByte( Item, 16 );
					Bytes.Add( ByteToAdd );
				}
			}

			return Bytes.GetBytes( );
		}
	}
}
